/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.handler.component;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import org.apache.solr.common.params.QueryElevationParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
import org.apache.lucene.util.StringHelper;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.DOMUtil;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.core.Config;
import org.apache.solr.core.SolrCore;
import org.apache.solr.schema.StrField;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.SortSpec;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.update.UpdateHandler;
import org.apache.solr.util.VersionedFile;
import org.apache.solr.util.RefCounted;
import org.apache.solr.util.plugin.SolrCoreAware;
import org.apache.solr.request.SolrQueryRequest;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

/**
 * A component to elevate some documents to the top of the result set.
 * 
 * @version $Id: QueryElevationComponent.java 1175532 2011-09-25 19:30:34Z rmuir $
 * @since solr 1.3
 */

public class QueryElevationComponent{
	
}
//
//public class QueryElevationComponent extends SearchComponent implements SolrCoreAware
//{
//	
//  private static Logger log = LoggerFactory.getLogger(QueryElevationComponent.class);
//  
//  // Constants used in solrconfig.xml
//  static final String FIELD_TYPE = "queryFieldType";
//  static final String CONFIG_FILE = "config-file";
//  static final String EXCLUDE = "exclude";
//  
//  // Runtime param -- should be in common?
//
//  private SolrParams initArgs = null;
//  private Analyzer analyzer = null;
//  private String idField = null;
//
//  boolean forceElevation = false;
//  // For each IndexReader, keep a query->elevation map
//  // When the configuration is loaded from the data directory.
//  // The key is null if loaded from the config directory, and
//  // is never re-loaded.
//  final Map<IndexReader,Map<String, ElevationObj>> elevationCache =
//    new WeakHashMap<IndexReader, Map<String,ElevationObj>>();
//
//  class ElevationObj {
//    final String text;
//    final String analyzed;
//    final BooleanClause[] exclude;
//    final BooleanQuery include;
//    final Map<String,Integer> priority;
//    
//    // use singletons so hashCode/equals on Sort will just work
//    final FieldComparatorSource comparatorSource;
//
//    ElevationObj( String qstr, List<String> elevate, List<String> exclude ) throws IOException
//    {
//      this.text = qstr;
//      this.analyzed = getAnalyzedQuery( this.text );
//      
//      this.include = new BooleanQuery();
//      this.include.setBoost( 0 );
//      this.priority = new HashMap<String, Integer>();
//      int max = elevate.size()+5;
//      for( String id : elevate ) {
//        TermQuery tq = new TermQuery( new Term( idField, id ) );
//        include.add( tq, BooleanClause.Occur.SHOULD );
//        this.priority.put( id, max-- );
//      }
//      
//      if( exclude == null || exclude.isEmpty() ) {
//        this.exclude = null;
//      }
//      else {
//        this.exclude = new BooleanClause[exclude.size()];
//        for( int i=0; i<exclude.size(); i++ ) {
//          TermQuery tq = new TermQuery( new Term( idField, exclude.get(i) ) );
//          this.exclude[i] = new BooleanClause( tq, BooleanClause.Occur.MUST_NOT );
//        }
//      }
//
//      this.comparatorSource = new ElevationComparatorSource(priority);
//    }
//  }
//  
//  @Override
//  public void init( NamedList args )
//  {
//    this.initArgs = SolrParams.toSolrParams( args );
//  }
//  
//  public void inform(SolrCore core)
//  {
//    String a = initArgs.get( FIELD_TYPE );
//    if( a != null ) {
//      FieldType ft = core.getSchema().getFieldTypes().get( a );
//      if( ft == null ) {
//        throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
//            "Unknown FieldType: '"+a+"' used in QueryElevationComponent" );
//      }
//      analyzer = ft.getQueryAnalyzer();
//    }
//
//    SchemaField sf = core.getSchema().getUniqueKeyField();
//    if( sf == null || !(sf.getType() instanceof StrField)) {
//      throw new SolrException( SolrException.ErrorCode.SERVER_ERROR, 
//          "QueryElevationComponent requires the schema to have a uniqueKeyField implemented using StrField" );
//    }
//    idField = StringHelper.intern(sf.getName());
//    
//    forceElevation = initArgs.getBool( QueryElevationParams.FORCE_ELEVATION, forceElevation );
//    try {
//      synchronized( elevationCache ) {
//        elevationCache.clear();
//        String f = initArgs.get( CONFIG_FILE );
//        if( f == null ) {
//          throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
//              "QueryElevationComponent must specify argument: '"+CONFIG_FILE
//              +"' -- path to elevate.xml" );
//        }
//        File fC = new File( core.getResourceLoader().getConfigDir(), f );
//        File fD = new File( core.getDataDir(), f );
//        if( fC.exists() == fD.exists() ) {
//          throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
//              "QueryElevationComponent missing config file: '"+f + "\n"
//              +"either: "+fC.getAbsolutePath() + " or " + fD.getAbsolutePath() + " must exist, but not both." );
//        }
//        if( fC.exists() ) {
//          log.info( "Loading QueryElevation from: "+fC.getAbsolutePath() );
//          Config cfg = new Config( core.getResourceLoader(), f );
//          elevationCache.put(null, loadElevationMap( cfg ));
//        }
//        else {
//          // preload the first data
//          RefCounted<SolrIndexSearcher> searchHolder = null;
//          try {
//            searchHolder = core.getSearcher(UpdateHandler.UPDATEPARTION,false,false);
//            IndexReader reader = searchHolder.get().getReader();
//            getElevationMap( reader, core );
//          } finally {
//            if (searchHolder != null) searchHolder.decref();
//          }
//        }
//      }
//    }
//    catch( Exception ex ) {
//      throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
//          "Error initializing QueryElevationComponent.", ex );
//    }
//  }
//
//  Map<String, ElevationObj> getElevationMap( IndexReader reader, SolrCore core ) throws Exception
//  {
//    synchronized( elevationCache ) {
//      Map<String, ElevationObj> map = elevationCache.get( null );
//      if (map != null) return map;
//
//      map = elevationCache.get( reader );
//      if( map == null ) {
//        String f = initArgs.get( CONFIG_FILE );
//        if( f == null ) {
//          throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
//                  "QueryElevationComponent must specify argument: "+CONFIG_FILE );
//        }
//        log.info( "Loading QueryElevation from data dir: "+f );
//
//        InputStream is = VersionedFile.getLatestFile( core.getDataDir(), f );
//        Config cfg = new Config( core.getResourceLoader(), f, new InputSource(is), null );
//        map = loadElevationMap( cfg );
//        elevationCache.put( reader, map );
//      }
//      return map;
//    }
//  }
//  
//  private Map<String, ElevationObj> loadElevationMap( Config cfg ) throws IOException
//  {
//    XPath xpath = XPathFactory.newInstance().newXPath();
//    Map<String, ElevationObj> map = new HashMap<String, ElevationObj>();
//    NodeList nodes = (NodeList)cfg.evaluate( "elevate/query", XPathConstants.NODESET );
//    for (int i=0; i<nodes.getLength(); i++) {
//      Node node = nodes.item( i );
//      String qstr = DOMUtil.getAttr( node, "text", "missing query 'text'" );
//      
//      NodeList children = null;
//      try {
//        children = (NodeList)xpath.evaluate("doc", node, XPathConstants.NODESET);
//      } 
//      catch (XPathExpressionException e) {
//        throw new SolrException( SolrException.ErrorCode.SERVER_ERROR, 
//            "query requires '<doc .../>' child" );
//      }
//
//      ArrayList<String> include = new ArrayList<String>();
//      ArrayList<String> exclude = new ArrayList<String>();
//      for (int j=0; j<children.getLength(); j++) {
//        Node child = children.item(j);
//        String id = DOMUtil.getAttr( child, "id", "missing 'id'" );
//        String e = DOMUtil.getAttr( child, EXCLUDE, null );
//        if( e != null ) {
//          if( Boolean.valueOf( e ) ) {
//            exclude.add( id );
//            continue;
//          }
//        }
//        include.add( id );
//      }
//      
//      ElevationObj elev = new ElevationObj( qstr, include, exclude );
//      if( map.containsKey( elev.analyzed ) ) {
//        throw new SolrException( SolrException.ErrorCode.SERVER_ERROR, 
//            "Boosting query defined twice for query: '"+elev.text+"' ("+elev.analyzed+"')" );
//      }
//      map.put( elev.analyzed, elev );
//    }
//    return map;
//  }
//  
//  /**
//   * Helpful for testing without loading config.xml
//   * @throws IOException 
//   */
//  void setTopQueryResults( IndexReader reader, String query, String[] ids, String[] ex ) throws IOException
//  {
//    if( ids == null ) {
//      ids = new String[0];
//    }
//    if( ex == null ) {
//      ex = new String[0];
//    }
//    
//    Map<String,ElevationObj> elev = elevationCache.get( reader );
//    if( elev == null ) {
//      elev = new HashMap<String, ElevationObj>();
//      elevationCache.put( reader, elev );
//    }
//    ElevationObj obj = new ElevationObj( query, Arrays.asList(ids), Arrays.asList(ex) );
//    elev.put( obj.analyzed, obj );
//  }
//  
//  String getAnalyzedQuery( String query ) throws IOException
//  {
//    if( analyzer == null ) {
//      return query;
//    }
//    StringBuilder norm = new StringBuilder();
//    TokenStream tokens = analyzer.reusableTokenStream( "", new StringReader( query ) );
//    tokens.reset();
//    
//    CharTermAttribute termAtt = tokens.addAttribute(CharTermAttribute.class);
//    while( tokens.incrementToken() ) {
//      norm.append( termAtt.buffer(), 0, termAtt.length() );
//    }
//    tokens.end();
//    tokens.close();
//    return norm.toString();
//  }
//
//  //---------------------------------------------------------------------------------
//  // SearchComponent
//  //---------------------------------------------------------------------------------
//  
//  @Override
//  public void prepare(ResponseBuilder rb) throws IOException
//  {
//    SolrQueryRequest req = rb.req;
//    SolrParams params = req.getParams();
//    // A runtime param can skip 
//    if( !params.getBool( QueryElevationParams.ENABLE, true ) ) {
//      return;
//    }
//
//    boolean exclusive = params.getBool(QueryElevationParams.EXCLUSIVE, false);
//    // A runtime parameter can alter the config value for forceElevation
//    boolean force = params.getBool( QueryElevationParams.FORCE_ELEVATION, forceElevation );
//    
//    Query query = rb.getQuery();
//    String qstr = rb.getQueryString();
//    if( query == null || qstr == null) {
//      return;
//    }
//
//    qstr = getAnalyzedQuery(qstr);
//    IndexReader reader = req.getSearcher().getReader();
//    ElevationObj booster = null;
//    try {
//      booster = getElevationMap( reader, req.getCore() ).get( qstr );
//    }
//    catch( Exception ex ) {
//      throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
//          "Error loading elevation", ex );      
//    }
//    
//    if( booster != null ) {
//      // Change the query to insert forced documents
//      if (exclusive == true){
//        //we only want these results
//        rb.setQuery(booster.include);
//      } else {
//        BooleanQuery newq = new BooleanQuery( true );
//        newq.add( query, BooleanClause.Occur.SHOULD );
//        newq.add( booster.include, BooleanClause.Occur.SHOULD );
//        if( booster.exclude != null ) {
//          for( BooleanClause bq : booster.exclude ) {
//            newq.add( bq );
//          }
//        }
//        rb.setQuery( newq );
//      }
//
//      
//      // if the sort is 'score desc' use a custom sorting method to 
//      // insert documents in their proper place 
//      SortSpec sortSpec = rb.getSortSpec();
//      if( sortSpec.getSort() == null ) {
//        sortSpec.setSort( new Sort( new SortField[] {
//            new SortField(idField, booster.comparatorSource, false ),
//            new SortField(null, SortField.SCORE, false)
//        }));
//      }
//      else {
//        // Check if the sort is based on score
//        boolean modify = false;
//        SortField[] current = sortSpec.getSort().getSort();
//        ArrayList<SortField> sorts = new ArrayList<SortField>( current.length + 1 );
//        // Perhaps force it to always sort by score
//        if( force && current[0].getType() != SortField.SCORE ) {
//          sorts.add( new SortField(idField, booster.comparatorSource, false ) );
//          modify = true;
//        }
//        for( SortField sf : current ) {
//          if( sf.getType() == SortField.SCORE ) {
//            sorts.add( new SortField(idField, booster.comparatorSource, sf.getReverse() ) );
//            modify = true;
//          }
//          sorts.add( sf );
//        }
//        if( modify ) {
//          sortSpec.setSort( new Sort( sorts.toArray( new SortField[sorts.size()] ) ) );
//        }
//      }
//    }
//    
//    // Add debugging information
//    if( rb.isDebug() ) {
//      List<String> match = null;
//      if( booster != null ) {
//        // Extract the elevated terms into a list
//        match = new ArrayList<String>(booster.priority.size());
//        for( Object o : booster.include.clauses() ) {
//          TermQuery tq = (TermQuery)((BooleanClause)o).getQuery();
//          match.add( tq.getTerm().text() );
//        }
//      }
//      
//      SimpleOrderedMap<Object> dbg = new SimpleOrderedMap<Object>();
//      dbg.add( "q", qstr );
//      dbg.add( "match", match );
//      rb.addDebugInfo( "queryBoosting", dbg );
//    }
//  }
//
//  @Override
//  public void process(ResponseBuilder rb) throws IOException {
//    // Do nothing -- the real work is modifying the input query
//  }
//    
//  //---------------------------------------------------------------------------------
//  // SolrInfoMBean
//  //---------------------------------------------------------------------------------
//
//  @Override
//  public String getDescription() {
//    return "Query Boosting -- boost particular documents for a given query";
//  }
//
//  @Override
//  public String getVersion() {
//    return "$Revision: 1175532 $";
//  }
//
//  @Override
//  public String getSourceId() {
//    return "$Id: QueryElevationComponent.java 1175532 2011-09-25 19:30:34Z rmuir $";
//  }
//
//  @Override
//  public String getSource() {
//    return "$URL: https://svn.apache.org/repos/asf/lucene/dev/branches/lucene_solr_3_5/solr/core/src/java/org/apache/solr/handler/component/QueryElevationComponent.java $";
//  }
//
//  @Override
//  public URL[] getDocs() {
//    try {
//      return new URL[] {
//        new URL("http://wiki.apache.org/solr/QueryElevationComponent")
//      };
//    } 
//    catch (MalformedURLException e) {
//      throw new RuntimeException( e );
//    }
//  }
//}
//
//class ElevationComparatorSource extends FieldComparatorSource {
//  private final Map<String,Integer> priority;
//
//  public ElevationComparatorSource( final Map<String,Integer> boosts) {
//    this.priority = boosts;
//  }
//
//  @Override
//  public FieldComparator<Integer> newComparator(final String fieldname, final int numHits, int sortPos, boolean reversed) throws IOException {
//    return new FieldComparator<Integer>() {
//      
//      FieldCache.StringIndex idIndex;
//      private final int[] values = new int[numHits];
//      int bottomVal;
//
//      @Override
//      public int compare(int slot1, int slot2) {
//        return values[slot2] - values[slot1];  // values will be small enough that there is no overflow concern
//      }
//
//      @Override
//      public void setBottom(int slot) {
//        bottomVal = values[slot];
//      }
//
//      private int docVal(int doc) throws IOException {
//        String id = idIndex.lookup[idIndex.order[doc]];
//        Integer prio = priority.get(id);
//        return prio == null ? 0 : prio.intValue();
//      }
//
//      @Override
//      public int compareBottom(int doc) throws IOException {
//        return docVal(doc) - bottomVal;
//      }
//
//      @Override
//      public void copy(int slot, int doc) throws IOException {
//        values[slot] = docVal(doc);
//      }
//
//      @Override
//      public void setNextReader(IndexReader reader, int docBase) throws IOException {
//        idIndex = FieldCache.DEFAULT.getStringIndex(reader, fieldname);
//      }
//
//      @Override
//      public Integer value(int slot) {
//        return values[slot];
//      }
//      
//      @Override
//      public double docValue(int doc) {
//        return Double.valueOf(0);//docVal(doc);
//      }
//    };
//  }
//}
